package com.iceframework.core.utils;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class BeanUtils {

    /**
     * Copies properties from source object to target object.
     * Only copies fields with matching names and compatible types.
     *
     * @param source the source object
     * @param target the target object
     * @throws IllegalArgumentException if source or target is null
     */
    public static void copyProperties(Object source, Object target) {
        if (source == null || target == null) {
            throw new IllegalArgumentException("Source and target objects must not be null");
        }

        // Get fields of source and target classes
        Map<String, Field> sourceFields = getFieldMap(source.getClass());
        Map<String, Field> targetFields = getFieldMap(target.getClass());

        // Iterate through source fields
        for (Map.Entry<String, Field> sourceEntry : sourceFields.entrySet()) {
            String fieldName = sourceEntry.getKey();
            Field sourceField = sourceEntry.getValue();

            // Check if target has a matching field
            if (targetFields.containsKey(fieldName)) {
                Field targetField = targetFields.get(fieldName);

                // Check if field types are compatible
                if (isTypeCompatible(sourceField.getType(), targetField.getType())) {
                    try {
                        // Make fields accessible if they are private
                        sourceField.setAccessible(true);
                        targetField.setAccessible(true);

                        // Get value from source and set to target
                        Object value = sourceField.get(source);
                        targetField.set(target, value);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException("Failed to copy field: " + fieldName, e);
                    }
                }
            }
        }
    }

    /**
     * Creates a map of field names to Field objects for a given class.
     * Includes inherited fields.
     *
     * @param clazz the class to get fields from
     * @return a map of field names to Field objects
     */
    private static Map<String, Field> getFieldMap(Class<?> clazz) {
        Map<String, Field> fieldMap = new HashMap<>();
        Class<?> currentClass = clazz;

        // Traverse class hierarchy to include inherited fields
        while (currentClass != null && currentClass != Object.class) {
            for (Field field : currentClass.getDeclaredFields()) {
                fieldMap.putIfAbsent(field.getName(), field);
            }
            currentClass = currentClass.getSuperclass();
        }

        return fieldMap;
    }

    /**
     * Checks if the source type is compatible with the target type for assignment.
     *
     * @param sourceType the type of the source field
     * @param targetType the type of the target field
     * @return true if types are compatible, false otherwise
     */
    private static boolean isTypeCompatible(Class<?> sourceType, Class<?> targetType) {
        // Exact match
        if (sourceType.equals(targetType)) {
            return true;
        }

        // Handle primitive and wrapper types
        if (sourceType.isPrimitive()) {
            sourceType = primitiveToWrapper(sourceType);
        }
        if (targetType.isPrimitive()) {
            targetType = primitiveToWrapper(targetType);
        }

        // Check if source type is assignable to target type
        return targetType.isAssignableFrom(sourceType);
    }

    /**
     * Converts a primitive type to its wrapper class.
     *
     * @param primitiveType the primitive type
     * @return the corresponding wrapper class
     */
    private static Class<?> primitiveToWrapper(Class<?> primitiveType) {
        if (primitiveType == int.class) return Integer.class;
        if (primitiveType == long.class) return Long.class;
        if (primitiveType == double.class) return Double.class;
        if (primitiveType == float.class) return Float.class;
        if (primitiveType == boolean.class) return Boolean.class;
        if (primitiveType == byte.class) return Byte.class;
        if (primitiveType == short.class) return Short.class;
        if (primitiveType == char.class) return Character.class;
        return primitiveType;
    }
}